#include "stdafx.h"
#include "ClientConnection.h"
#include "Logger.h"
#include "CoreXMLHandlerManager.h"
#include "XMLConnection.h"

#include "Poco\Exception.h"
#include "Poco\Delegate.h"
#include "Poco\ThreadPool.h"
#include "Poco\Format.h"
#include "Poco\Thread.h"

namespace ExamSystem
{
    using Poco::Exception;
    using Poco::Delegate;
    using Poco::ThreadPool;
    using Poco::Thread;

    CExamClientConnection::CExamClientConnection(void)
        : m_xmlHandlerManager(new CCoreXMLHandlerManager)
        , m_flagClosingConnection(false)
        , m_synDisconnecting(false)
        , m_flagReconnecting(false)
    {

    }

    CExamClientConnection::~CExamClientConnection(void)
    {

    }

    void CExamClientConnection::SetServerAddress(const std::string &host, Poco::UInt16 port)
    {
        m_serverAddress = SocketAddress::SocketAddress(host, port);
    }

    void CExamClientConnection::SetServerAddress(const SocketAddress &socketAddress)
    {
        m_serverAddress = socketAddress;
    }

    bool CExamClientConnection::ConnectServer(void)
    {
        try
        {
            m_clientSocket = new StreamSocket;
            m_clientSocket->connect(m_serverAddress);
            systemLogger.information("Connected to server " + m_serverAddress.toString());

            m_xmlConnection = new CXMLConnection(*m_clientSocket, m_xmlHandlerManager, false);
            m_xmlConnection->eventConnectionClosed += 
                Delegate<CExamClientConnection, int>(this, &CExamClientConnection::OnConnectionClosed);

            ThreadPool::defaultPool().start(*m_xmlConnection);

            // the client will start the stream
            m_xmlConnection->StartStream();

            // wait the stream start
            m_xmlConnection->synStreamStart.wait();

            if (!m_xmlConnection->IsStreamStarted())
                return false;
        }
        catch (Exception &exc)
        {
            systemLogger.error(exc.displayText());
            return false;
        }

        return true;
    }

    void CExamClientConnection::OnConnectionClosed(const void* pSender, int &id)
    {
        eventConnectionDisconnected(this, id);
        if (m_flagClosingConnection)
        {
            // this is a voluntary closure
            //m_xmlConnection = NULL;
            //m_clientSocket = NULL;
            m_synDisconnecting.set();
            eventConnectionClosed(this, id);
            return;
        }

        // otherwise, we should reconnect the server
        if (m_flagReconnecting)
            systemLogger.information("Reconnecting...");
        else
            systemLogger.error("Disconnected from server, reconnecting...");
        // as we are still in current connection's stack, we should hold it until we
        // finish reconnecting
        m_deadXmlConnection = m_xmlConnection;
        m_deadClientSocket = m_clientSocket;

        m_xmlConnection = NULL;
        m_clientSocket = NULL;

        static const int sleepTime = 5000;
        static const int maxReconnectingTime = 10;
        int nReconnectingTime = 0;
        do 
        {
            if (!ConnectServer())
            {
                m_xmlConnection = NULL;
                m_clientSocket = NULL;
                nReconnectingTime++;
                if (nReconnectingTime < maxReconnectingTime)
                {
                    systemLogger.error(Poco::format("The (%d/%d)-th try failed. Wait 5 seconds to retry...", nReconnectingTime, maxReconnectingTime));
                    Thread::current()->sleep(sleepTime);
                }
                else
                {   
                    systemLogger.error(Poco::format("The (%d/%d)-th try failed.", nReconnectingTime, maxReconnectingTime));
                    break;
                }
            }
            else
            {
                eventConnectionReconnected(this, id);
                break;
            }
        } while (true);

        if (nReconnectingTime == maxReconnectingTime)
        {
            // failed
            systemLogger.fatal("Failed to reconnect.");
            eventConnectionClosed(this, id);
        }

        m_flagReconnecting = false;
    }

    void CExamClientConnection::DisconnectServer(bool wait /*= true*/)
    {
        if (!m_xmlConnection)
            return;

        m_flagClosingConnection = true;
        m_xmlConnection->EndStream();

        if (!wait)
            return;

        // wait the disconnecting finished
        try 
        {
            m_synDisconnecting.wait(10000);
        }
        catch (...)
        {
            m_xmlConnection->CloseConnection();
        }
    }

    bool CExamClientConnection::SendXMLStanza(Document &xmlStanza)
    {
        if (m_xmlConnection)
            return m_xmlConnection->SendXMLStanza(xmlStanza);

        return false;
    }

    bool CExamClientConnection::SendRawByte(const void *data, int length)
    {
        if (m_xmlConnection)
            return m_xmlConnection->SendRawByte(data, length);

        return false;
    }

    SharedPtr<CCoreXMLHandlerManager> & CExamClientConnection::GetXMLHandlerManager(void)
    {
        return m_xmlHandlerManager;
    }

    SharedPtr<CXMLConnection> & CExamClientConnection::GetXMLConnection(void)
    {
        return m_xmlConnection;
    }

    bool CExamClientConnection::ReconnectServer(void)
    {
        // here we just disconnect the server
        // the auto-reconnect feather will start to reconnect
        if (m_flagReconnecting)
            return false;

        m_flagReconnecting = true;
        m_xmlConnection->EndStream();

        return true;
    }

}